/**
 * LUA 工具模块
 */
#include <errno.h>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#include <minwindef.h>
#include <conio.h>
#elif (__linux__ || __unix__)
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#else // vitis ?
#endif

#include <lua.h>
#include <lauxlib.h>
#include "utils.h"
#include "mcu.h"
#include "lua_utils.h"

#include "log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "LUA-UTILS"
#include "log4app.h"

lua_State* _lua4regexp = NULL;
static lua_CFunction string_find = NULL;

int lua_regexp(const char* str, const char* regexp) {
	if (!_lua4regexp) return -1;
	lua_State* L = _lua4regexp;

	if (!string_find) {
		// 第一次调用？取出 string.find() 对应的 C 实现函数
		lua_getglobal(L, "string");    // luastack +1
		lua_getfield(L, -1, "find");   // luastack +1
		string_find = lua_tocfunction(L, -1);
		lua_pop(L, 2);                 // luastack -2
	}
	if (!string_find) return -1;

	// 调用 string.find(str, regexp, 1, false)
	lua_pushcfunction(L, string_find); // luastack +1
	lua_pushstring(L, str);			   // luastack +1
	lua_pushstring(L, regexp);		   // luastack +1
	lua_pushinteger(L, 1);			   // luastack +1
	lua_pushboolean(L, 0);             // luastack +1
	int err = lua_pcall(L, 4, 2, 0);   // luastack -5, +1(调用失败)或+2(调用成功)
	if (err) {
		// 调用失败时，栈上只有 1 个错误字符串
		lua_pop(L, 1);
		return -1;
	}

	// 调用成功，则有 2 个返回值
	int ps;//, pe;
	if (lua_isnil(L, -2)) {
		// 如果匹配失败，则返回 nil
		ps = -1;
	} else {
		// 如果匹配成功，则会返回 2 个匹配位置指示
		ps = (int)lua_tointeger(L, -2);
		//pe = (int)lua_tointeger(L, -1);
	}

	// !!! 注意: 返回前一定要用 lua_pop() 清理干净 lua 栈，不要影响 lua 调用栈 !!!
	lua_pop(L, 2);
	return ps;
}

// 调用 LUA string.find(str, regexp, 1, false) 匹配字符串
int trx_regexp(const char* str, const char* regexp) {
	return lua_regexp(str, regexp);
}

int luaL_fillstringarray(lua_State* L, int idx, const char **strarr) {
	if(!L || !strarr || !lua_istable(L, idx)) return 0;
	int count = 0;
	if(idx < 0) idx--; // 如果数组索引 idx 是倒数的，则因为压入了字符串值要减一
	while(*strarr) {
		lua_pushstring(L, *strarr); lua_rawseti(L, idx, ++count);
		strarr++;
	}
	return count;
}

int _outlog(lua_State* L) {
	if (!_logger) return 0;
	int level = luaL_checkint(L, 1);
	const char* tag = luaL_optstring(L, 2, NULL);
	const char* msg = luaL_checkstring(L, 3);
	_logger->log(_logger, level, tag, "%s", msg);
	return 0;
}

int _dumpbin(lua_State* L) {
	if (!_logger) return 0;
	size_t size = 0, len = 0;
	const char *buf = NULL;

	// 检出 buffer 参数
	luaL_checkbuffer(L, 1, buf, size, len);
	int cpl = (int)luaL_optinteger(L, 3, 16);   // default 16
	int show_offset = luaL_optboolean(L, 4, true); // default true
	int show_ascii = luaL_optboolean(L, 5, true);  // default true

	_logger->dumpbin(_logger, (const void*)buf, len, cpl, show_offset, show_ascii);
	return 0;
}

// "cmem" 型 userdata 的 metatable 函数 __len()，用于返回 cmem 的大小
static int _cmem_meta_op_len(lua_State* L) {
	size_t len = lua_objlen(L, 1);
	lua_pushinteger(L, len);
	return 1;
}

void* luaL_newcmem(lua_State* L, size_t size) {
	// 创建一个大小为 size 的 userdata
	void *userdata = lua_newuserdata(L, size);

	// 创建一个 metatable
	luaL_newmetatable(L, "cmem");

	// 为 metatable 添加一个函数 __len()，用于返回 cmem 的大小
	lua_pushstring(L, "__len");
	lua_pushcfunction(L, _cmem_meta_op_len);
	lua_rawset(L, -3);

	// 把 metatable 设置为 userdata 的 metatable
	lua_setmetatable(L, -2);

	// 返回 userdata
	return userdata;
}

int _newcmem(lua_State* L) {
	size_t size = luaL_checkinteger(L, 1);
	if (size < 1) luaL_error(L, "invalid size !");
	luaL_newcmem(L, size);
	return 1;
}

int _newstrbuf(lua_State* L) {
	size_t size = luaL_checkinteger(L, 1);
	if (size < 1) luaL_error(L, "invalid size !");
	// 随便复制一块内存，做为 string 初值
	lua_pushlstring(L, (const char*)L, size);
	return 1;
}

int _malloc(lua_State* L) {
	size_t size = luaL_checkinteger(L, 1);
	if (size < 1) luaL_error(L, "invalid size !");
	void* mem = malloc(size);
	if (!mem) luaL_error(L, "malloc() failed !");
	lua_pushlightuserdata(L, mem);
	lua_pushinteger(L, size);
	return 2;
}

int _free(lua_State* L) {
	if (!lua_islightuserdata(L, 1)) luaL_error(L, "invalid pointer !");
	free(lua_touserdata(L, 1));
	return 0;
}

int _isbinary(lua_State* L) {
	char ch;
	size_t len = 0;
	const char *str = luaL_checklstring(L, 1, &len);
	len = luaL_optinteger(L, 2, len);

	for (size_t i = 0; i < len; i++) {
		ch = str[i];
		if (ch < 0x20 && ch != '\t' && ch != '\r' && ch != '\n') {
			lua_pushboolean(L, true);
			return 1;
		}
	}

	lua_pushboolean(L, false);
	return 1;
}

int _dbgflag(lua_State* L) {
	int idx = luaL_checkint(L, 1);
	if (idx >= sizeof(size_t) * 8) luaL_error(L, "Invalid flag index, should be: 0 ~ %d", sizeof(size_t) * 8 - 1);
	int val = (int)luaL_optinteger(L, 2, -1);
	lua_pushinteger(L, dbgflag_get(idx)); // 返回当前值
	if (val >= 0) dbgflag_set(idx, val);
	return 1;
}

int _dbgbreak(lua_State* L) {
	dbgbreak();
	return 0;
}

int _msleep(lua_State* L) {
	unios_ms_t val = (unios_ms_t)luaL_checklong(L, 1);
	unios_ms_delay(val);
	return 0;
}

bool luaL_isincomplete(lua_State *L, int status) {
    if(status == LUA_ERRSYNTAX) {
        // 语法错误: 根据错误原因判断是否是脚本不完整
        // 如果错误消息以 "... near '<eof>'" 结尾，则表示代码不完整
        size_t lmsg;
        const char *msg = lua_tolstring(L, -1, &lmsg);
        const char *tp = msg + lmsg - sizeof("'<eof>'") + 1;
        if(memcmp(tp, "'<eof>'", sizeof("'<eof>'")) == 0) return true;
    }
    return false;
}

std_err_t luaL_pushtrunk(lua_State *L, const char *script, size_t size) {
    // 加载脚本，返回错误时，会在栈顶压入错误消息
    int status = luaL_loadbuffer(L, script, size, "=stdin");
    // 完整且正确，脚本已经编译成函数，并压入栈顶
    if(status == 0) return STD_ERR_OK;

    std_err_t err = STD_ERR_FAIL;
    if(luaL_isincomplete(L, status)) {
        err = STD_ERR_CONTINUE;
    } else if(status == LUA_ERRMEM) {
        err = STD_ERR_NO_MEM;
    }

    // 弹出 luaL_loadbuffer() 压入栈顶的错误消息
    lua_pop(L, 1);
    return err;
}

static void l_message(const char *msg) {
	fputs(msg, stderr);
	fputs("\n", stderr);
	fflush(stderr);
}

static int report(lua_State *L, int status) {
	if (status && !lua_isnil(L, -1)) {
		const char *msg = lua_tostring(L, -1);
    	if (msg == NULL) msg = "(error object is not a string)";
		l_message(msg);
		lua_pop(L, 1);
	}
	return status;
}

static int traceback(lua_State *L) {
	if (!lua_isstring(L, 1)) /* 'message' not a string? */
		return 1; /* keep it intact */
	lua_getfield(L, LUA_GLOBALSINDEX, "debug");
	if (!lua_istable(L, -1)) {
		lua_pop(L, 1);
		return 1;
	}
	lua_getfield(L, -1, "traceback");
	if (!lua_isfunction(L, -1)) {
		lua_pop(L, 2);
		return 1;
	}
	lua_pushvalue(L, 1); /* pass error message */
	lua_pushinteger(L, 2); /* skip this function and traceback */
	lua_call(L, 2, 1); /* call debug.traceback */
	return 1;
}

static int docall(lua_State *L, int narg, int clear) {
	int status;
	int base = lua_gettop(L) - narg; /* function index */
	lua_pushcfunction(L, traceback); /* push traceback function */
	lua_insert(L, base); /* put it under chunk and args */
	status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base);
	lua_remove(L, base); /* remove traceback function */
	/* force a complete garbage collection in case of errors */
	if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0);
	return status;
}

static void print_version(void) {
	l_message(LUA_RELEASE "  " LUA_COPYRIGHT);
}

int luaL_dolibrary(lua_State *L, const char *name) {
	lua_getglobal(L, "require");
	lua_pushstring(L, name);
	return report(L, docall(L, 1, 1));
}

// LUA Shell 底层数据结构
typedef struct _luashell {
    lua_State* L;                   // LUA 解释器
    transceiver_t *trx;             // shell 数据收发器
    char bufin[LUAL_BUFFERSIZE];    // 用于存放输入的代码的缓冲区
    size_t bufinsz;                 // bufin 中有效数据长度
} luashell_t;

static void _luashell_on_rx_line(size_t cookie_size, void* cookie) {
    if(cookie_size < sizeof(void*) || !cookie) return;
    luashell_t *shell = (luashell_t*)unios_get_pointer(cookie);
    if(!shell || !shell->L || !shell->bufinsz || !shell->trx) return;
    size_t lmsg;
    const char *msg = NULL;
    lua_State* L = shell->L;
    
    // 加载脚本，返回错误时，会在栈顶压入错误消息
    int status = luaL_loadbuffer(L, shell->bufin, shell->bufinsz, "=stdin");
    
    if(status == LUA_ERRSYNTAX) {
        // 语法错误: 根据错误原因判断是否是脚本不完整
        // 如果错误消息以 "... near '<eof>'" 结尾，则表示代码不完整
        msg = lua_tolstring(L, -1, &lmsg);
        const char *tp = msg + lmsg - sizeof("'<eof>'") + 1;
        // 弹出 luaL_loadbuffer() 压入栈顶的错误消息
        lua_pop(L, 1);
        // 代码还不完整，需要继续补充完成
        if(memcmp(tp, "'<eof>'", sizeof("'<eof>'")) == 0) return;
    } else if(status == 0) {
        // 代码加载成功，执行它
    	status = docall(L, 0, 0);
		report(L, status);
        // any result to print ?
		if (status == 0 && lua_gettop(L) > 0) { 
			lua_getglobal(L, "print");
			lua_insert(L, 1);
			if (lua_pcall(L, lua_gettop(L) - 1, 0, 0) != 0) l_message(lua_pushfstring(L, "error calling " LUA_QL("print") " (%s)", lua_tostring(L, -1)));
		}
        // 清除缓冲区重新开始
        shell->bufinsz = 0;
        return;
    }
    
    // 其它错误，需要清除缓冲区重新开始
    shell->bufinsz = 0;
    // 发回错误信息
    shell->trx->tx(shell->trx, msg, lmsg, NULL);
}

static void _luashell_trx_on_rx_data(void *cookie, transceiver_t *trx, void* buf, size_t size, size_t len, void* slice, size_t slice_size) {
	if(!cookie || !slice || !slice_size) return;
    luashell_t *shell = (luashell_t*)cookie;
    
    if(sizeof(shell->bufin) - shell->bufinsz < slice_size) {
        // 代码缓冲区溢出
        loge("LUA shell input buffer overflow !\r\n");
        return;
    }
    
    // 把收到的数据拷贝到代码缓冲区
    memcpy(shell->bufin + shell->bufinsz, slice, slice_size); shell->bufinsz += slice_size;
    
    // 检查代码是否有行结束标志
    char ch = shell->bufin[shell->bufinsz - 1];
    if(ch == '\n') {
        // 有行结束标志，创建一个任务以进一步检查并执行脚本
        // !!! 注意：当前在中断处理函数中，要创建一个延时处理命令的任务，在中断外处理命令，以免阻碍中断及时返回 !!!
        unios_task_run_later(_luashell_on_rx_line, sizeof(void*), &shell);
    }
}

std_err_t luashell_listen_on(lua_State* L, transceiver_t *trx) {
	if(!L) return STD_ERR_INVALID_ARG;
    
    // 获取 LUA 解释器中全局变量 _luashell 保存的 luashell_t 数据结构
    lua_getglobal(L, "_luashell");
    luashell_t *shell = (luashell_t*)lua_touserdata(L, -1); lua_pop(L, 1);
    
    if(shell == NULL) {
        // 停止监听
        if(trx == NULL) return STD_ERR_OK;
        
        // LUA 解释器中没有全局变量 _luashell，需要初始化它
        shell = (luashell_t*)unios_malloc(sizeof(luashell_t));
        if(shell == NULL) return STD_ERR_NO_MEM;
        shell->L = L;
        shell->trx = trx;
        shell->bufinsz = 0;
        lua_pushlightuserdata(L, shell); lua_setglobal(L, "_luashell");
    }
    
    if(shell->L != L) {
        // 解释器指针不匹配？错误的状态！
        return STD_ERR_INVALID_STATE;
    }

	// 如果之前已经与其它 trx 绑定，则解除之前的绑定
	if(shell->trx && shell->trx != trx) {
		shell->trx->on_rx_data = NULL;
		shell->trx->on_rx_data_cookie = NULL;
	}

    if(trx == NULL) {
        // 删除 LUA 解释器中全局变量 _luashell
        lua_pushnil(L); lua_setglobal(L, "_luashell");
        // 释放 _luashell 分配的内存
        unios_free(shell);
        return STD_ERR_OK;
    }

	// 与 trx 建立绑定关系
	trx->on_rx_data = _luashell_trx_on_rx_data;
	trx->on_rx_data_cookie = shell;
    print_version();
    
    // 清除栈上所有数据，重新开始
    lua_settop(L, 0);

	// trx 开始异步监听
	return trx->listen(trx, true);
}


#ifdef _WIN32

int _ioctl(lua_State* L) {
	luaL_error(L, std_err_comment(STD_ERR_NOT_SUPPORTED));
	return 0;
}

int _readstdin(lua_State* L) {
	bool echo = luaL_optboolean(L, 1, false);
	long timeout_ms = (long)luaL_optlong(L, 2, 0);
	int (*getchfunc)() = echo ? _getche : _getch;
	char ch;

	if(!timeout_ms) {
		if(kbhit()) { ch = getchfunc(); lua_pushlstring(L, &ch, 1); return 1; }
		return 0;
	}

	for(unios_ms_t tms = unios_ms_now(); timeout_ms < 0 || unios_ms_elapsed(tms) < timeout_ms; unios_ms_delay(1)) {
		if(kbhit()) { ch = getchfunc(); lua_pushlstring(L, &ch, 1); return 1; }
	}
	return 0; // 超时
}

int _readline(lua_State* L) {
	const char *line = luaL_optstring(L, 1, NULL);
	if(!kbhit()) {
		// 键盘无输入, 第 1 个参数就是传进来的 line, 只需压入第 2 个参数即可
		lua_pushboolean(L, false);
		return 2;
	}

	char ch = _getche();
	if(line) {
		// 在输入的 line 后面添加新输入的字符
		luaL_Buffer buf;
		luaL_buffinit(L, &buf);
		luaL_addstring(&buf, line);
		luaL_addchar(&buf, ch);
		if(ch == '\r') { luaL_addchar(&buf, '\n'); ch = '\n'; } // Windows 中按回车键时 _getche() 只会返回 '\r'，需要再添加一个 '\n' !!!
		luaL_pushresult(&buf);
	} else {
		// 返回一个只有一个字符的字符串
		lua_pushlstring(L, &ch, 1);
	}
	lua_pushboolean(L, ch == '\n');
	return 2;
}

#elif (__linux__ || __unix__)

int _ioctl(lua_State* L) {
	int fd = luaL_checkint(L, 1);
	unsigned long flag = luaL_checklong(L, 2);
	int attr = luaL_checkint(L, 3);
	int ret = ioctl(fd, flag, &attr);
	if(ret) luaL_error(L, strerror(errno));
	return 0;
}

// 判断文件缓冲区中有多少数据可读
static inline int available_bytes_to_read(int fd) {
    int count = 0;
    return (ioctl(fd, FIONREAD, &count) == -1) ? 0 : count;
}

int _readstdin(lua_State* L) {
	bool echo = luaL_optboolean(L, 1, false); // 不起作用?
	long timeout_ms = (long)luaL_optlong(L, 2, 0);
	char ch;

	if(!timeout_ms) {
		if(available_bytes_to_read(STDIN_FILENO)) { ch = getchar(); lua_pushlstring(L, &ch, 1); return 1; }
		return 0;
	}

	for(unios_ms_t tms = unios_ms_now(); timeout_ms < 0 || unios_ms_elapsed(tms) < timeout_ms; unios_ms_delay(1)) {
		if(available_bytes_to_read(STDIN_FILENO)) { ch = getchar(); lua_pushlstring(L, &ch, 1); return 1; }
	}
	return 0; // 超时
}

int _readline(lua_State* L) {
	const char *line = luaL_optstring(L, 1, NULL);
	if(!available_bytes_to_read(STDIN_FILENO)) {
		// 键盘无输入, 第 1 个参数就是传进来的 line, 只需压入第 2 个参数即可
		lua_pushboolean(L, false);
		return 2;
	}

	char ch = getchar();
	if(line) {
		luaL_Buffer buf;
		luaL_buffinit(L, &buf);
		luaL_addstring(&buf, line);
		luaL_addchar(&buf, ch);
		luaL_pushresult(&buf);
	} else {
		lua_pushlstring(L, &ch, 1);
	}
	lua_pushboolean(L, ch == '\r' || ch == '\n');
	return 2;
}

#else // vitis ?

int _ioctl(lua_State* L) {
	luaL_error(L, std_err_comment(STD_ERR_NOT_SUPPORTED));
	return 0;
}

int _readstdin(lua_State* L) {
	luaL_error(L, std_err_comment(STD_ERR_NOT_SUPPORTED));
	return 0;
}

int _readline(lua_State* L) {
	luaL_error(L, std_err_comment(STD_ERR_NOT_SUPPORTED));
	return 0;
}

#endif


static const struct luaL_Reg _lib[] = {
	{ "_outlog", _outlog},
	{ "_dumpbin", _dumpbin},
	{ "_newcmem", _newcmem},
	{ "_newstrbuf", _newstrbuf},
	{ "_malloc", _malloc},
	{ "_free", _free},
	{ "_isbinary", _isbinary},
	{ "_dbgflag", _dbgflag},
	{ "_dbgbreak", _dbgbreak},
	{ "_msleep", _msleep},
	{ "_ioctl", _ioctl},
	{ "_readstdin", _readstdin},
	{ "_readline", _readline},
	{ NULL, NULL },
};

static struct {
	char* name;
	int32_t value;
} _consts[] = {
	{"IOCTL_CMD_FIONBIO", 0x5421},
	{NULL, 0}
};

int luaopen_lutils(lua_State* L) {
#if LUA_VERSION_NUM < 502
	luaL_register(L, "lutils", _lib);
#else
	luaL_newlib(L, _lib);
#endif
	for (int i = 0; _consts[i].name != NULL; i++) {
		lua_pushstring(L, _consts[i].name);
		lua_pushnumber(L, _consts[i].value);
		lua_settable(L, -3);
	}
	return 1;
}
